﻿// ===============================================================================
//  Copyright © ymind.net. All rights reserved .
//  官方网站：http://ymind.net/
//  版权所有：彦铭工作室 老陈出品
// ===============================================================================

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using MongoDB.Bson;
using MongoDB.Bson.IO;

namespace Lcsoft.Data
{
	/// <summary>
	/// 封装程序集 <see cref="Lcsoft.Data"/> 的常用操作。
	/// </summary>
	public static class DataHelper
	{
		private const RegexOptions _REGEX_OPTIONS_COMPILED_SINGLELINE = RegexOptions.Compiled | RegexOptions.Singleline;

		/// <summary>
		/// 将指定的集合或数组类型转换为 <see cref="BsonValue"/> 对象。
		/// </summary>
		/// <typeparam name="TObject">要转换的集合或数组的项的类型。</typeparam>
		/// <param name="obj">要转换的集合或数组。</param>
		/// <returns>返回 <see cref="BsonValue"/> 对象。</returns>
		public static BsonValue ConvertToBsonValue<TObject>(IEnumerable<TObject> obj)
		{
			if (obj == null) throw new ArgumentNullException("obj");

			var doc = new{
				Value = obj
			}.ToBsonDocument();

			return doc["Value"];
		}

		/// <summary>
		/// 返回表示当前对象的 JSON 表示形式。
		/// </summary>
		/// <typeparam name="T">要序列化的对象的类型。</typeparam>
		/// <param name="obj">要序列化的对象。</param>
		/// <param name="indent">指定一个值，该值指示返回值是否允许格式缩进。</param>
		/// <param name="encoding">指定字符编码。</param>
		/// <param name="newLineChars">指定换行符，默认使用“\n”。</param>
		/// <param name="indentChar">指定格式缩进时使用的字符。</param>
		/// <param name="indentation">指定缩进大小。</param>
		/// <param name="outputMode">指定输出模式。</param>
		/// <returns>返回 <see cref="System.String"/>。</returns>
		public static string ToJson<T>(T obj, bool indent = false, Encoding encoding = null, string newLineChars = "\n", char indentChar = '\t', int indentation = 1, JsonOptimizationTarget outputMode = JsonOptimizationTarget.Strict)
		{
			if (Equals(obj, default(T))) return obj.ToString();

			if (indentChar != ' ' && indentChar != '\t') throw new ArgumentOutOfRangeException("indentChar");

			if (indentation < 1) indentation = 1;
			if (indentation > 8) indentation = 8;

			var settings = JsonWriterSettings.Defaults.Clone();
			settings.Encoding = encoding ?? Encoding.UTF8;
			settings.Indent = indent;
			settings.IndentChars = String.Empty.PadLeft(indentation, indentChar);
			settings.NewLineChars = newLineChars;
			settings.OutputMode = JsonOutputMode.JavaScript;
			settings.GuidRepresentation = GuidRepresentation.CSharpLegacy;

			var enumerableObj = obj as IEnumerable<T>;
			var json = enumerableObj == null ? obj.ToJson(settings) : ConvertToBsonValue(enumerableObj).ToJson();

			return MongoDBJsonCleanup(json, outputMode);
		}

		/// <summary>
		/// 清理 MongoDB 驱动生成的 Json 数据。
		/// </summary>
		/// <param name="json">要清理的 Json 数据。</param>
		/// <param name="outputMode">指定输出模式。</param>
		/// <returns>返回 <see cref="System.String"/>。</returns>
		public static string MongoDBJsonCleanup(string json, JsonOptimizationTarget outputMode = JsonOptimizationTarget.Default)
		{
			if (String.IsNullOrWhiteSpace(json)) throw new ArgumentOutOfRangeException("json");

			var output = json;

			switch (outputMode)
			{
				case JsonOptimizationTarget.Default:
				case JsonOptimizationTarget.JavaScript:
					output = _JavaScriptJsonOutputMode(output);
					break;
				case JsonOptimizationTarget.JsonP:
					output = _JavaScriptJsonPOutputMode(output);
					break;
				case JsonOptimizationTarget.PHP:
					output = _PhpJsonOutputMode(output);
					break;
				case JsonOptimizationTarget.Strict:
					output = _StrictJsonOutputMode(output);
					break;
			}

			return output;
		}

		private static string _JavaScriptJsonOutputMode(string json)
		{
			if (String.IsNullOrWhiteSpace(json)) throw new ArgumentOutOfRangeException("json");

			var output = json;

			// 日期处理："2012-12-06 07:51:47"
			var dateRegex = new Regex(@"(new )?Date\((-?\d+)\)", _REGEX_OPTIONS_COMPILED_SINGLELINE);

			foreach (Match match in dateRegex.Matches(output))
			{
				var dateTime = BsonUtils.ToDateTimeFromMillisecondsSinceEpoch(Convert.ToInt64(match.Groups[2].Value));

				output = output.Replace(match.Groups[0].Value, String.Format(@"""{0}""", dateTime.ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.CurrentCulture)));
			}

			return output;
		}

		private static string _JavaScriptJsonPOutputMode(string json)
		{
			if (String.IsNullOrWhiteSpace(json)) throw new ArgumentOutOfRangeException("json");

			// 日期处理：new Date(1354780307143)
			return Regex.Replace(json, @"(new )?Date\((-?\d+)\)", "new Date($2)", _REGEX_OPTIONS_COMPILED_SINGLELINE);
		}

		private static string _PhpJsonOutputMode(string json)
		{
			if (String.IsNullOrWhiteSpace(json)) throw new ArgumentOutOfRangeException("json");

			var output = json;

			// 日期处理：1354780307
			var dateRegex = new Regex(@"(new )?Date\((-?\d+)\)", _REGEX_OPTIONS_COMPILED_SINGLELINE);

			foreach (Match match in dateRegex.Matches(output))
			{
				var ticks = Convert.ToInt64(match.Groups[2].Value)*10000;
				var dateSeconds = (ticks < 0 ? (ticks + BsonConstants.UnixEpoch.Ticks) : ticks)/10000000;

				output = output.Replace(match.Groups[0].Value, dateSeconds.ToString(CultureInfo.InvariantCulture));
			}

			return output;
		}

		private static string _StrictJsonOutputMode(string json)
		{
			if (String.IsNullOrWhiteSpace(json)) throw new ArgumentOutOfRangeException("json");

			var output = json;

			// 日期处理："Thu, 06 Dec 2012 07:51:47 GMT"
			var dateRegex = new Regex(@"(new )?Date\((-?\d+)\)", _REGEX_OPTIONS_COMPILED_SINGLELINE);

			foreach (Match match in dateRegex.Matches(output))
			{
				var dateTime = BsonUtils.ToDateTimeFromMillisecondsSinceEpoch(Convert.ToInt64(match.Groups[2].Value));

				output = output.Replace(match.Groups[0].Value, String.Format(@"""{0}""", dateTime.ToUniversalTime().ToString("r", CultureInfo.CurrentCulture)));
			}

			return output;
		}
	}
}
